﻿using Bouyei.GeoCore.Geometries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Bouyei.GeoCore.GeoParsers.Wkt
{
    public class GeometryReader : BaseString
    {
        const string bracketContent = @"(?<=\()[^\(\)]*(?=\))";//只是匹配括号内容
        const string brackentOut = @"(?<=\().*(?=\))";//只匹配最外层括号内容

        static readonly char[] comma = new char[] { ',' };
        static readonly char[] space = new char[] { ' ' };
        readonly Regex regContent;
        readonly Regex regOutside;

        public GeoType GeometryType { get; private set; }

        public GeometryReader(string wkt)
        : base(wkt)
        {
            regContent = new Regex(bracketContent);
            regOutside = new Regex(brackentOut);
        }

        public List<List<Coordinate[]>> ReaderCoodinates()
        {
            int index = Content.IndexOf('(');
            if (index < 0) throw new Exception("not recoginze wkt format");

            string type = Regex.Replace(Content.Substring(0, index), @"\s", "").ToUpper();

            if (type == "POINTZ"
                || type == "POINTM"
                || type == "POINTZM")
            {
                GeometryType = GeoType.POINTZ;
            }
            else if (type == "LINESTRINGZ"
                || type == "LINESTRINGM"
                || type == "LINESTRINGZM")
            {
                GeometryType = GeoType.POINT;
            }
            else if (type == "POLYGONZ"
                || type == "POLYGONM"
                || type == "POLYGONZM")
            {
                GeometryType = GeoType.POLYGON;
            }
            else if (type == "MULTIPOLYGONZ"
                || type == "MULTIPOLYGONM"
                || type == "MULTIPOLYGONZM")
            {
                GeometryType = GeoType.MULTIPOLYGON;
            }
            else
            {
                GeometryType = (GeoType)Enum.Parse(typeof(GeoType), type);
            }
            List<List<Coordinate[]>> ls = new List<List<Coordinate[]>>(2);

            switch (GeometryType)
            {
                case GeoType.POINT:
                    {
                        var matchs = regContent.Matches(Content);
                        var m = MultiPoint(matchs);
                        ls.Add(m);
                    }
                    break;
                case GeoType.LINESTRING:
                    {
                        var matchs = regContent.Matches(Content);
                        var m = Linestring(matchs);
                        ls.Add(m);
                    }
                    break;
                case GeoType.POLYGON:
                    {
                        var matchs = regContent.Matches(Content);
                        var m = Polygon(matchs);
                        ls.Add(m);
                    }
                    break;
                case GeoType.MULTIPOINT:
                    {
                        var matchs = regContent.Matches(Content);
                        var m = MultiPoint(matchs);
                        ls.Add(m);
                    }
                    break;
                case GeoType.MULTILINESTRING:
                    {
                        var matchs = regContent.Matches(Content);
                        var m = Linestring(matchs);
                        ls.Add(m);
                    }
                    break;
                case GeoType.MULTIPOLYGON:
                    {
                        var r = MultiPolygon(Content);
                        ls = r;
                    }
                    break;
                case GeoType.GEOMETRYCOLLECTION:
                    throw new Exception("geometryType not supported" + GeometryType);
                default:
                    return null;
            }
            return ls;
        }

        public Geometry Reader()
        {
            var coordsArray = ReaderCoodinates();

            Geometry geo = null;
            switch (GeometryType)
            {
                case GeoType.POINT:
                    geo = new Geometry(coordsArray[0][0][0]);
                    break;
                case GeoType.LINESTRING:
                    geo = new Geometry(GeometryType, coordsArray[0][0]);
                    break;
                case GeoType.POLYGON:
                    if (coordsArray.Count == 1)
                        geo = new Geometry(GeometryType, coordsArray[0]);
                    else if (coordsArray.Count > 1)
                        geo = new Geometry(GeometryType, coordsArray[0]);
                    break;
                case GeoType.MULTIPOINT:
                    geo = new Geometry(GeometryType, coordsArray[0][0]);
                    break;
                case GeoType.MULTILINESTRING:
                    geo = new Geometry(GeometryType, coordsArray[0]);
                    break;
                case GeoType.MULTIPOLYGON:
                    {
                        List<GeoPolygon> geos = new List<GeoPolygon>(coordsArray.Count);

                        foreach (var polygon in coordsArray)
                        {
                            if (polygon.Count == 1)
                                geos.Add(new GeoPolygon(polygon[0]));
                            else if (polygon.Count >= 2)
                            {
                                List<Coordinate[]> interior = new List<Coordinate[]>(polygon.Count - 1);

                                for (int i = 0; i < interior.Capacity; i++)
                                {
                                    interior.Add(polygon[i + 1]);
                                }

                                geos.Add(new GeoPolygon(polygon[0], interior));
                            }
                        }
                        geo = new Geometry(geos.ToArray());
                    }
                    break;
                default:
                    throw new Exception("not support geometry type:" + GeometryType.ToString());
            }
            return geo;
        }

        private List<Coordinate[]> Polygon(MatchCollection matchs)
        {
            return MatchCollectionTo(matchs);
        }

        private List<Coordinate[]> MultiPoint(MatchCollection matchs)
        {
            return MatchCollectionTo(matchs);
        }

        private List<Coordinate[]> Linestring(MatchCollection matchs)
        {
            return MatchCollectionTo(matchs);
        }

        private List<List<Coordinate[]>> MultiPolygon(string wkt)
        {
            var matches = regOutside.Matches(wkt);
            List<List<Coordinate[]>> multi = new List<List<Coordinate[]>>(64);

            foreach (Match match in matches)
            {
                string valString = match.Value;
                int sIndex = valString.IndexOf("((");
                for (int i = sIndex; i < valString.Length;)
                {
                    if (sIndex != -1)
                    {
                        int eIndex = valString.IndexOf("))", sIndex + 1);
                        if (eIndex != -1)
                        {
                            string polygon = valString.Substring(sIndex, eIndex - sIndex + 2);
                            var rings = regContent.Matches(polygon);
                            List<Coordinate[]> coordinates = MatchCollectionTo(rings);
                            multi.Add(coordinates);

                            if (eIndex + 3 >= valString.Length)
                                break;

                            sIndex = valString.IndexOf("((", eIndex + 3);
                            continue;
                        }
                    }
                    break;
                }
            }
            return multi;
        }

        private List<Coordinate[]> MatchCollectionTo(MatchCollection matchs)
        {
            List<Coordinate[]> coordinates = new List<Coordinate[]>(matchs.Count);

            foreach (Match m in matchs)
            {
                var geo = m.Value.Split(comma);
                Coordinate[] coords = new Coordinate[geo.Length];

                for (int i = 0; i < coords.Length; ++i)
                {
                    string lonlat = geo[i];

                    var point = lonlat.Trim(' ').Split(space, StringSplitOptions.RemoveEmptyEntries);
                    coords[i] = new Coordinate()
                    {
                        X = double.Parse(point[0]),
                        Y = double.Parse(point[1])
                    };
                }
                coordinates.Add(coords);
            }
            return coordinates;
        }
    }
}
